import 'package:al_utils/al_utils.dart';

extension IndexedIterable<E> on Iterable<E> {
  Iterable<T> mapIndexed<T>(T Function(E e, int i) f) {
    var i = 0;
    return map((e) => f(e, i++));
  }

  int sumBy(int Function(E) selector) {
    return or().map(selector).fold(0, (prev, curr) => prev + curr);
  }

  /// Example:
  /// ```dart
  /// [1.5, 2.5].sumByDouble((d) => 0.5 * d); // 2.0
  /// ```
  double sumByDouble(num Function(E) selector) {
    return or().map(selector).fold(0.0, (prev, curr) => prev + curr);
  }

  double? averageBy(num Function(E) selector) {
    if (or().isEmpty) {
      return null;
    }

    return sumByDouble(selector) / length;
  }

  Iterable<E> operator -(Iterable<E> other) => where((element) => !other.contains(element));
}

//final flat = [
//     [[1, 2, 3], 4, 5],
//     [6, [7, [8, 9]], 10],
//     11,12
//   ].flatten().log();
//   (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
extension Flatten<T extends Object> on Iterable<T> {
  Iterable<T> flatten() {
    Iterable<T> _flatten(Iterable<T> list) sync* {
      for (final value in list) {
        if (value is List<T>) {
          yield* _flatten(value);
        } else {
          yield value;
        }
      }
    }

    return _flatten(this);
  }
}

extension ListExtensions<E> on List<E> {
  void forEachIndexed(void Function(int index, E element) action) {
    for (var index = 0; index < length; index++) {
      action(index, this[index]);
    }
  }

  Iterable<E> splice(int start, int count, [List<E>? insert]) {
    final result = [...getRange(start, start + count)];
    replaceRange(start, start + count, insert ?? []);
    return result;
  }
}

extension Group<T> on Iterable<T> {
  Map<K, Iterable<T>> groupBy<K>(K Function(T) key) {
    final map = <K, List<T>>{};
    for (final element in this) {
      final keyValue = key(element);
      if (!map.containsKey(keyValue)) {
        map[keyValue] = [];
      }
      map[keyValue]?.add(element);
    }
    return map;
  }
}

extension ListExtension<T> on List<T?>? {
  T? get firstObject => this == null || this!.isEmpty ? null : this!.first;

  List<T> unwrap() => (this ?? []).whereType<T>().toList();
}

// extension ListOptionExtension<T> on List<T?> {
//   List<T> validate() {
//     return List<T>.from(where((element) => (element != null)).toList());
//   }
// }

extension Duplicates<T> on List<T> {
  void addAllByAvoidingDuplicates(Iterable<T> values) => replaceRange(0, length, {
    ...([...this] + [...values])
  });

  int get numberOfDuplicates => length - {...this}.length;

  bool get containsDuplicates => numberOfDuplicates > 0;

  List<T> get uniques => [
    ...{...this}
  ];

  void removeDuplicates() => replaceRange(
    0,
    length,
    uniques,
  );

  List<T> get duplicates => [
    for (var i = 0; i < length; i++) [...this].skip(i + 1).contains(this[i]) ? this[i] : null
  ].whereType<T>().toList();

  List<E> compactMap<E>(E? Function(T element) f) {
    Iterable<E> imp(E? Function(T element) f) sync* {
      for (final value in this) {
        final mapped = f(value);
        if (mapped != null) {
          yield mapped;
        }
      }
    }

    return imp(f).toList();
  }
}

extension FlatMap<T> on T? {
  E? flatMap<E>(E? Function(T value) f) {
    if (this != null) {
      return f(this as T);
    } else {
      return null;
    }
  }
}
